<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module" src="../../polymer-element.js"></script>
<body>

  <dom-module id="my-element">
    <template>
      <div id="content">{{prop}}</div>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';

class MyElement extends PolymerElement {

  static get is() { return 'my-element'; }

  static get properties() {
    return {
      prop: {
        value: 'base',
        observer: '_propObserver'
      },
      computedPropDep: {
        value: true
      },
      computedProp: {
        computed: '_compute(computedPropDep)'
      },
      accessor: String,
      noStomp: String
    };
  }

  static get observers() {
    return [
      '_methodObserver(prop)'
    ];
  }

  constructor() {
    super();
    this._propObserver = sinon.spy();
    this._methodObserver = sinon.spy();
    this._tapListener = sinon.spy();
    this._calledConstructor++;
  }

  get noStomp() {
    return 'noStomp';
  }

  connectedCallback() {
    super.connectedCallback();
    this._calledConnectedCallback++;
  }

  attributeChangedCallback() {
    super.attributeChangedCallback.apply(this, arguments);
    this._callAttributeChangedCallback++;
  }

  ready() {
    super.ready();
    this._ensureAttribute('tabIndex', 0);
    this.addEventListener('click', (e) => this._tapListener(e));
    this._calledReady++;
  }

  _compute(value) {
    return value;
  }
}

MyElement.prototype._calledConstructor = 0;
MyElement.prototype._calledConnectedCallback = 0;
MyElement.prototype._calledReady = 0;
MyElement.prototype._callAttributeChangedCallback = 0;

customElements.define(MyElement.is, MyElement);

window.MyElement = MyElement;
</script>
  </dom-module>


  <dom-module id="sub-element">
    <script type="module">
import '../../polymer-element.js';

class SubElement extends window.MyElement {

  static get is() { return 'sub-element'; }

  static get properties() {
    return {
      prop: {
        value: 'sub'
      },
      prop2: {
        value: 'prop2',
        observer: '_prop2Observer'
      },
      computedPropDep: {
        computed: '_compute(prop2)'
      }
    };
  }

  static get observers() {
    return [
      '_subMethodObserver(prop, prop2)'
    ];
  }

  constructor() {
    super();
    this._calledConstructor++;
    this._prop2Observer = sinon.spy();
    this._subMethodObserver = sinon.spy();
  }

  connectedCallback() {
    super.connectedCallback();
    this._calledConnectedCallback++;
  }

  ready() {
    super.ready();
    this._calledReady++;
    this._ensureAttribute('role', 'button');
    this.addEventListener('click', (e) => this._tapListener(e));
  }

}

customElements.define(SubElement.is, SubElement);

window.SubElement = SubElement;
</script>
  </dom-module>

  <dom-module id="sub-mixin-element">
    <script type="module">
import '../../polymer-element.js';

function MyMixin(Base) {
  return class extends Base {

    static get properties() {
      return {
        prop: {
          value: 'mixin'
        },
        mixin: {
          value: 'mixin',
          observer: '_mixinObserver'
        }
      };
    }

    static get observers() {
      return [
        '_mixinMethodObserver(mixin, prop, prop2)'
      ];
    }

    constructor() {
      super();
      this._calledConstructor++;
      this._mixinObserver = sinon.spy();
      this._mixinMethodObserver = sinon.spy();
    }

    connectedCallback() {
      super.connectedCallback();
      this._calledConnectedCallback++;
    }

    ready() {
      super.ready();
      this._calledReady++;
      this._ensureAttribute('mixinattr', 'mixinattr');
      this.addEventListener('click', (e) => this._tapListener(e));
    }

  };
}

class SubMixinElement extends MyMixin(window.SubElement) {

  static get is() { return 'sub-mixin-element'; }

  static get properties() {
    return {
      prop3: {
        value: 'prop3',
        observer: '_prop3Observer'
      }
    };
  }

  static get observers() {
    return [
      '_subMixinMethodObserver(mixin, prop, prop2, prop3)'
    ];
  }

  constructor() {
    super();
    this._calledConstructor++;
    this._prop3Observer = sinon.spy();
    this._subMixinMethodObserver = sinon.spy();
  }

  connectedCallback() {
    super.connectedCallback();
    this._calledConnectedCallback++;
  }

  ready() {
    super.ready();
    this._calledReady++;
    this._ensureAttribute('submixinattr', 'submixinattr');
    this.addEventListener('click', (e) => this._tapListener(e));
  }

}

customElements.define(SubMixinElement.is, SubMixinElement);

window.SubMixinElement = SubMixinElement;
</script>
  </dom-module>

  <script type="module">
import { html } from '../../polymer-element.js';

class SubNewTemplate extends window.MyElement {
  static get template() {
    return html`
      <h1>Sub template</h1>
      <div id="subContent">{{prop2}}</div>`;
  }

  static get properties() {
    return {
      prop2: {
        value: 'prop2',
      }
    };
  }
}

customElements.define('sub-new-template', SubNewTemplate);

/**
 * Instead of overriding the static getter, this element has its template
 * assigned directly to the constructor.
 */
class SubNewTemplateAssigned extends window.MyElement {
  static get properties() {
    return {
      prop2: {
        value: 'prop2',
      }
    };
  }
}

SubNewTemplateAssigned.template = html`
  <h1>Sub template</h1>
  <div id="subContent">{{prop2}}</div>`;

customElements.define('sub-new-template-assigned', SubNewTemplateAssigned);
</script>

  <template id="no-dom-module">
    <style>
    :host {
      display: block;
      border: 10px solid rgb(123, 123, 123);
    }
    </style>
    <div id="child">Content</div>
  </template>
  <script type="module">
import { PolymerElement } from '../../polymer-element.js';
class NoDomModule extends PolymerElement {
  static get is() {return 'no-dom-module';}
  static get template() {
    return document.body.querySelector('template#no-dom-module');
  }
}
customElements.define('no-dom-module', NoDomModule);
</script>

  <dom-module id="sub-extended-template">
    <script type="module">
import '../../polymer-element.js';
// Setting up property bind listeners in both base and sub class
// is required to catch regression bug
class ElementWithBindListeners extends window.MyElement {
  static get is() { return 'element-with-bind-listeners'; }
}
class SubExtendedTemplate extends ElementWithBindListeners {
  static get is() { return 'sub-extended-template'; }
  static get template() {
    if (!this.hasOwnProperty('_template')) {
      const baseClass = Object.getPrototypeOf(this.prototype).constructor;
      this._template = baseClass.template.cloneNode(true);
      const newElement = document.createElement('div');
      newElement.innerHTML = '<div id="subContent" imaginary-prop$="{{prop}}""></div>';
      this._template.content.appendChild(newElement.firstChild);
    }
    return this._template;
  }
}
customElements.define(ElementWithBindListeners.is, ElementWithBindListeners);
customElements.define(SubExtendedTemplate.is, SubExtendedTemplate);
</script>
  </dom-module>

  <test-fixture id="my-element-attr">
    <template>
      <my-element prop="attr" foo="foo"></my-element>
    </template>
  </test-fixture>

  <test-fixture id="sub-element-attr">
    <template>
      <sub-element prop="attr" prop2="attr"></sub-element>
    </template>
  </test-fixture>

  <test-fixture id="sub-mixin-element-attr">
    <template>
      <sub-mixin-element prop="attr" prop2="attr" prop3="attr"></sub-mixin-element>
    </template>
  </test-fixture>

  <script type="module">
import { PolymerElement } from '../../polymer-element.js';
suite('class extends Polymer.Element', function() {

  var el;

  setup(function() {
    el = document.createElement('my-element');
    document.body.appendChild(el);
  });

  teardown(function() {
    document.body.removeChild(el);
  });

  test('instanceof', function() {
    assert.instanceOf(el, HTMLElement);
    assert.instanceOf(el, PolymerElement);
    assert.instanceOf(el, window.MyElement);
  });

  test('lifecycle', function() {
    assert.equal(el._calledConstructor, 1);
    assert.equal(el._calledConnectedCallback, 1);
    assert.equal(el._calledReady, 1);
    assert.equal(el._callAttributeChangedCallback, 0);
  });

  test('template', function() {
    assert.equal(el.prop, 'base');
    assert.equal(el.$.content.textContent, el.prop);
  });

  test('observers', function() {
    assert.isTrue(el._propObserver.calledOnce);
    assert.isTrue(el._propObserver.calledWith('base'));
    assert.isTrue(el._methodObserver.calledOnce);
    assert.isTrue(el._methodObserver.calledWith('base'));
  });

  test('listeners', function() {
    el.dispatchEvent(new CustomEvent('click'));
    assert.equal(el._tapListener.callCount, 1);
  });

  test('properties', function() {
    assert.equal(el.prop, 'base');
    assert.equal(el.computedPropDep, true);
    assert.equal(el.computedProp, true);
  });

  test('attributes', function() {
    const fixtureEl = fixture('my-element-attr');
    assert.equal(fixtureEl.prop, 'attr');
    assert.equal(fixtureEl._callAttributeChangedCallback, 1);
    assert.isTrue(fixtureEl.hasAttribute('tabindex'));
  });

  test('reflecting attributes', function() {
    const fixtureEl = fixture('my-element-attr');
    fixtureEl.prop = 'propValue';
    fixtureEl._propertyToAttribute('prop');
    assert.equal(fixtureEl.getAttribute('prop'), 'propValue');
  });

  test('version', function() {
    assert.isOk(el.constructor.polymerElementVersion);
  });
});

suite('subclass', function() {

  let el;

  setup(function() {
    el = document.createElement('sub-element');
    document.body.appendChild(el);
  });

  teardown(function() {
    document.body.removeChild(el);
  });

  test('instanceof', function() {
    assert.instanceOf(el, HTMLElement);
    assert.instanceOf(el, PolymerElement);
    assert.instanceOf(el, window.MyElement);
    assert.instanceOf(el, window.SubElement);
  });

  test('lifecycle', function() {
    assert.equal(el._calledConstructor, 2);
    assert.equal(el._calledConnectedCallback, 2);
    assert.equal(el._calledReady, 2);
    assert.equal(el._callAttributeChangedCallback, 0);
  });

  test('template', function() {
    assert.equal(el.prop, 'sub');
    assert.equal(el.$.content.textContent, el.prop);
  });

  test('observers', function() {
    assert.isTrue(el._propObserver.calledOnce);
    assert.isTrue(el._propObserver.calledWith('sub'));
    assert.isTrue(el._methodObserver.calledOnce);
    assert.isTrue(el._methodObserver.calledWith('sub'));
    // sub
    assert.isTrue(el._prop2Observer.calledOnce);
    assert.isTrue(el._prop2Observer.calledWith('prop2'));
    assert.isTrue(el._subMethodObserver.calledOnce);
    assert.isTrue(el._subMethodObserver.calledWith('sub', 'prop2'));
  });

  test('listeners', function() {
    el.dispatchEvent(new CustomEvent('click'));
    assert.equal(el._tapListener.callCount, 2);
  });

  test('properties', function() {
    assert.equal(el.prop, 'sub');
    assert.equal(el.prop2, 'prop2');
    assert.equal(el.computedPropDep, 'prop2');
    assert.equal(el.computedProp, 'prop2', 'computedProp dependency cannot itself be a computed property');
  });

  test('attributes', function() {
    const fixtureEl = fixture('sub-element-attr');
    assert.equal(fixtureEl.prop, 'attr');
    assert.equal(fixtureEl.prop2, 'attr');
    assert.equal(fixtureEl._callAttributeChangedCallback, 2);
    assert.isTrue(fixtureEl.hasAttribute('tabindex'));
    assert.equal(fixtureEl.getAttribute('role'), 'button');
  });

});

suite('mixin', function() {

  let el;

  setup(function() {
    el = document.createElement('sub-mixin-element');
    document.body.appendChild(el);
  });

  teardown(function() {
    document.body.removeChild(el);
  });

  test('instanceof', function() {
    assert.instanceOf(el, HTMLElement);
    assert.instanceOf(el, PolymerElement);
    assert.instanceOf(el, window.MyElement);
    assert.instanceOf(el, window.SubElement);
    assert.instanceOf(el, window.SubMixinElement);
  });

  test('lifecycle', function() {
    assert.equal(el._calledConstructor, 4);
    assert.equal(el._calledConnectedCallback, 4);
    assert.equal(el._calledReady, 4);
    assert.equal(el._callAttributeChangedCallback, 0);
  });

  test('template', function() {
    assert.equal(el.prop, 'mixin');
    assert.equal(el.$.content.textContent, el.prop);
  });

  test('observers', function() {
    assert.isTrue(el._propObserver.calledOnce);
    assert.isTrue(el._propObserver.calledWith('mixin'));
    assert.isTrue(el._methodObserver.calledOnce);
    assert.isTrue(el._methodObserver.calledWith('mixin'));
    // sub
    assert.isTrue(el._prop2Observer.calledOnce);
    assert.isTrue(el._prop2Observer.calledWith('prop2'));
    assert.isTrue(el._subMethodObserver.calledOnce);
    assert.isTrue(el._subMethodObserver.calledWith('mixin', 'prop2'));
    // mixin
    assert.isTrue(el._mixinObserver.calledOnce);
    assert.isTrue(el._mixinObserver.calledWith('mixin'));
    assert.isTrue(el._mixinMethodObserver.calledOnce);
    assert.isTrue(el._mixinMethodObserver.calledWith('mixin', 'mixin', 'prop2'));
    // submixin
    assert.isTrue(el._prop3Observer.calledOnce);
    assert.isTrue(el._prop3Observer.calledWith('prop3'));
    assert.isTrue(el._subMixinMethodObserver.calledOnce);
    assert.isTrue(el._subMixinMethodObserver.calledWith('mixin', 'mixin', 'prop2', 'prop3'));

  });

  test('listeners', function() {
    el.dispatchEvent(new CustomEvent('click'));
    assert.equal(el._tapListener.callCount, 4);
  });

  test('attributes', function() {
    const fixtureEl = fixture('sub-mixin-element-attr');
    assert.equal(fixtureEl.prop, 'attr');
    assert.equal(fixtureEl.prop2, 'attr');
    assert.equal(fixtureEl.prop3, 'attr');
    assert.equal(fixtureEl._callAttributeChangedCallback, 3);
    assert.isTrue(fixtureEl.hasAttribute('tabindex'));
    assert.equal(fixtureEl.getAttribute('role'), 'button');
    assert.equal(fixtureEl.getAttribute('mixinattr'), 'mixinattr');
    assert.equal(fixtureEl.getAttribute('submixinattr'), 'submixinattr');
  });

});

suite('subclass new template', function() {
  let el;

  setup(function() {
    el = document.createElement('sub-new-template');
    document.body.appendChild(el);
  });

  teardown(function() {
    document.body.removeChild(el);
  });

  test('template', function() {
    assert.equal(el.prop2, 'prop2');
    assert.equal(el.$.subContent.textContent, el.prop2);
    assert.notOk(el.$.content);
  });

  suite('use without dom-module', function() {
    let el;

    setup(function() {
      el = document.createElement('no-dom-module');
      document.body.appendChild(el);
    });

    teardown(function() {
      document.body.removeChild(el);
    });

    test('template', function() {
      assert.ok(el.$.child);
      assert.equal(el.$.child.textContent, 'Content');
      assert.equal(window.ShadyCSS.getComputedStyleValue(el, 'border-top-width'), '10px');
    });
  });
});

suite('subclass new template assigned', function() {
  let el;

  setup(function() {
    el = document.createElement('sub-new-template-assigned');
    document.body.appendChild(el);
  });

  teardown(function() {
    document.body.removeChild(el);
  });

  test('template', function() {
    assert.equal(el.prop2, 'prop2');
    assert.equal(el.$.subContent.textContent, el.prop2);
    assert.notOk(el.$.content);
  });
});

suite('extend sub-class template', function() {
  let el, baseEl;
  setup(function() {
    el = document.createElement('sub-extended-template');
    document.body.appendChild(el);
    baseEl = document.createElement('element-with-bind-listeners');
    document.body.appendChild(baseEl);
  });
  teardown(function() {
    document.body.removeChild(el);
    document.body.removeChild(baseEl);
  });
  test('has original template', function() {
    assert.equal(el.prop, 'base');
    assert.equal(el.$.content.textContent, el.prop);
  });
  test('has new element', function() {
    assert.ok(el.$.subContent);
    assert.equal(el.$.subContent.getAttribute('imaginary-prop'), 'base');
  });
  test('base doesnt have new element', function() {
    assert.notOk(baseEl.$.subContent);
  });
});
</script>
</body>
</html>
